/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.admin.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.AllArgsConstructor;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.xssf.usermodel.*;
import org.springblade.common.utils.CommonUtil;
import org.springblade.common.utils.PoiExcelUtil;
import org.springblade.common.utils.PoiSelectExcelUtil;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tenant.annotation.TenantIgnore;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.modules.admin.dto.erp.serivce.PartnerCloudPartsListDTO;
import org.springblade.modules.admin.entity.ElevatorBrand;
import org.springblade.modules.admin.entity.MaintainParts;
import org.springblade.modules.admin.entity.PartsInfo;
import org.springblade.modules.admin.entity.PartsPrice;
import org.springblade.modules.admin.service.IPartsPriceService;
import org.springblade.modules.admin.service.MaintainPartsService;
import org.springblade.modules.admin.vo.PartsInfoVO;
import org.springblade.modules.admin.mapper.PartsInfoMapper;
import org.springblade.modules.admin.service.IPartsInfoService;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 配件信息表 服务实现类
 *
 * @author ysq
 * @since 2023-08-09
 */
@AllArgsConstructor
@Service
public class PartsInfoServiceImpl extends BaseServiceImpl<PartsInfoMapper, PartsInfo> implements IPartsInfoService {

	private final IPartsPriceService partsPriceService;
	private final MaintainPartsService maintainPartsService;
	private final BladeRedis bladeRedis;
	/**
	 * excel标题
	 */
	private static final String[] HEAD_TITLE = {"配件名称", "配件一级分类", "配件二级分类", "配件三级分类", "品牌", "规格型号", "单位"};
	private static final String EXCEL_ERR_CACHE = "parts:info:excel:err:";

	@Override
	public IPage<PartsInfoVO> selectPartsInfoPage(IPage<PartsInfoVO> page, PartsInfoVO partsInfo) {
		return page.setRecords(baseMapper.selectPartsInfoPage(page, partsInfo));
	}

	@Override
	public boolean submit(PartsInfo partsInfo) {
		if (!checkRepeat(partsInfo)) {
			throw new ServiceException("该配件信息已存在!");
		}
		return saveOrUpdate(partsInfo);
	}

	/**
	 * 校验重复
	 * @param partsInfo
	 * @return
	 */
	private boolean checkRepeat(PartsInfo partsInfo) {
		LambdaQueryWrapper<PartsInfo> condition = Wrappers.<PartsInfo>lambdaQuery()
			.eq(PartsInfo :: getPartsName, partsInfo.getPartsName())
			.eq(PartsInfo :: getBrand, partsInfo.getBrand())
			.eq(PartsInfo :: getModel, partsInfo.getModel())
			.eq(PartsInfo :: getUnit, partsInfo.getUnit());
		if (Func.isNotEmpty(partsInfo.getId())) {
			condition.ne(PartsInfo :: getId, partsInfo.getId());
		}
		return count(condition) > 0 ? false : true;
	}

	@Override
	public boolean batchRemoveParts(List<Long> ids) {
		int checkPartsPrice = partsPriceService.count(Wrappers.<PartsPrice>lambdaQuery().in(PartsPrice :: getPartsId, ids));
		if (checkPartsPrice > 0) {
			throw new ServiceException("配件信息已关联配件销售价格，无法删除");
		}
		return this.deleteLogic(ids);
	}

	@Override
	public void downElevatorTemplate(HttpServletResponse response) {
		XSSFWorkbook workbook = initExcelTemplate();
		String fileName = DateUtil.format(new Date(), "yyyyMMddHHmmss") + ".xlsx";
		CommonUtil.exportStream(workbook, response, fileName);
	}

	@Override
	public Map<String, Object> uploadExcel(MultipartFile file) {
		if (file.isEmpty()) {
			throw new ServiceException("上传文件不能为空");
		}

		Workbook wb = null;
		try {
			wb = WorkbookFactory.create(file.getInputStream());
		} catch (Exception e) {
			throw new ServiceException("请使用正确的电梯模板导入电梯");
		}
		List<Map<String, Object>> readAll = PoiExcelUtil.readExcel(wb, 0, 1, 2, 0);
		if (readAll.size() == 0) {
			throw new ServiceException("导入数据为空");
		}

		// 存储错误导出数据
		LinkedList<Map<String, Object>> errExportList = new LinkedList<>();
		// 存储错误提示数据
		List<Map<String,Object>> errTipList = new ArrayList<>();
		// 错误数量
		int errNum = 0;
		for (int i = 0; i < readAll.size(); i++) {
			PartsInfo partsInfo = new PartsInfo();
			Map<String, Object> itemMap = readAll.get(i);
			// 后面导入成功，会移除
			errExportList.addLast(itemMap);

			// 配件名称
			String name = Convert.toStr(itemMap.get(HEAD_TITLE[0]));
			if (Func.isBlank(name)) {
				itemMap.put("num", String.format("第%d条数据出错", i + 1));
				itemMap.put("errMsg","配件名称不能为空；");
				errTipList.add(itemMap);
				errNum++;
				continue;
			}
			partsInfo.setPartsName(name);

			// 配件分类
			MaintainParts parts = null;
			String classification1 = Convert.toStr(itemMap.get(HEAD_TITLE[1]));
			if (Func.isBlank(classification1)) {
				itemMap.put("num", String.format("第%d条数据出错", i + 1));
				itemMap.put("errMsg","配件分类不能为空；");
				errTipList.add(itemMap);
				errNum++;
				continue;
			}
			MaintainParts parts1 = getPartsByName(classification1, 1);
			if (Func.isEmpty(parts1)) {
				itemMap.put("num", String.format("第%d条数据出错", i + 1));
				itemMap.put("errMsg","未正确选择配件一级分类；");
				errTipList.add(itemMap);
				errNum++;
				continue;
			}
			String classification2 = Convert.toStr(itemMap.get(HEAD_TITLE[2]));
			if (Func.isEmpty(classification2)) {
				parts = parts1;
			} else {
				MaintainParts parts2 = getPartsByName(classification2, 2);
				if (Func.isEmpty(parts2)) {
					itemMap.put("num", String.format("第%d条数据出错", i + 1));
					itemMap.put("errMsg","未正确选择配件二级分类；");
					errTipList.add(itemMap);
					errNum++;
					continue;
				}
				String classification3 = Convert.toStr(itemMap.get(HEAD_TITLE[3]));
				if (Func.isEmpty(classification3)) {
					parts = parts2;
				} else {
					MaintainParts parts3 = getPartsByName(classification3, 3);
					if (Func.isEmpty(parts3)) {
						itemMap.put("num", String.format("第%d条数据出错", i + 1));
						itemMap.put("errMsg","未正确选择配件三级分类；");
						errTipList.add(itemMap);
						errNum++;
						continue;
					}
					parts = parts3;
				}
			}
			partsInfo.setMaintainPartsId(parts.getId());
			partsInfo.setMaintainPartsName(parts.getName());

			// 品牌
			String brand = Convert.toStr(itemMap.get(HEAD_TITLE[4]));
			if (Func.isEmpty(brand)) {
				itemMap.put("num", String.format("第%d条数据出错", i + 1));
				itemMap.put("errMsg","配件品牌不能为空；");
				errTipList.add(itemMap);
				errNum++;
				continue;
			}
			partsInfo.setBrand(brand);

			// 规格型号
			String model = Convert.toStr(itemMap.get(HEAD_TITLE[5]));
			if (Func.isEmpty(model)) {
				itemMap.put("num", String.format("第%d条数据出错", i + 1));
				itemMap.put("errMsg","规格型号不能为空；");
				errTipList.add(itemMap);
				errNum++;
				continue;
			}
			partsInfo.setModel(model);

			// 单位
			String unit = Convert.toStr(itemMap.get(HEAD_TITLE[6]));
			if (Func.isEmpty(unit)) {
				itemMap.put("num", String.format("第%d条数据出错", i + 1));
				itemMap.put("errMsg","单位不能为空；");
				errTipList.add(itemMap);
				errNum++;
				continue;
			}
			partsInfo.setUnit(unit);

			if (!checkRepeat(partsInfo)) {
				itemMap.put("num", String.format("第%d条数据出错", i + 1));
				itemMap.put("errMsg","该配件信息已存在；");
				errTipList.add(itemMap);
				errNum++;
				continue;
			}

			if (this.save(partsInfo)) {
				// 导入成功，从错误集合移除
				errExportList.removeLast();
			}
		}

		// 将出错数据存到redis
		if (errNum > 0) {
			bladeRedis.setEx(AuthUtil.getTenantId().concat(StringPool.COLON).concat(EXCEL_ERR_CACHE).concat(String.valueOf(AuthUtil.getUserId())), errExportList, 30000L);
		}

		Map<String, Object> resultInfo = new HashMap<>(8);
		if(errTipList.isEmpty()){
			resultInfo.put("result",Boolean.TRUE);
		}else {
			resultInfo.put("result",Boolean.FALSE);
		}
		resultInfo.put("total",readAll.size());
		resultInfo.put("succeed", readAll.size() - errNum);
		resultInfo.put("untreated", errTipList);
		return resultInfo;
	}

	/**
	 * 根据配件分类名称查询
	 * @param name
	 * @param level
	 * @return
	 */
	private MaintainParts getPartsByName(String name, Integer level) {
		return maintainPartsService.getOne(Wrappers.<MaintainParts>lambdaQuery()
			.eq(MaintainParts :: getLevel, level)
			.eq(MaintainParts :: getName, name), false);
	}

	@Override
	public void downErrExcel(HttpServletResponse response) {
		Object data = bladeRedis.get(AuthUtil.getTenantId().concat(StringPool.COLON).concat(EXCEL_ERR_CACHE).concat(String.valueOf(AuthUtil.getUserId())));
		if (Func.isEmpty(data)) {
			throw new ServiceException("未有错误数据导出！");
		}
		LinkedList<Map<String, Object>> errList = (LinkedList<Map<String, Object>>)data;

		XSSFWorkbook workbook = initExcelTemplate();
		XSSFSheet sheet = workbook.getSheetAt(0);
		for (int i = 0; i < errList.size(); i++) {
			Map<String, Object> itemMap = errList.get(i);
			XSSFRow row = sheet.createRow(i + 2);
			row.setHeightInPoints(20);
			for (int j = 0; j < HEAD_TITLE.length; j++) {
				XSSFCell cell = row.createCell(j);
				cell.setCellValue(Convert.toStr(itemMap.get(HEAD_TITLE[j])));
			}
		}

		String fileName = "partsInfo-error-" + DateUtil.format(new Date(), "yyyyMMddHHmmss") + ".xlsx";
		CommonUtil.exportStream(workbook, response, fileName);
	}

	@Override
	public void exportExcel(PartsInfoVO partsInfo, HttpServletResponse response) throws IOException {
		List<PartsInfoVO> list = baseMapper.selectPartsInfoPage(null, partsInfo);
		if (list.size() == 0) {
			throw new ServiceException("没有配件信息可以导出");
		}

		ExcelWriter excelWriter = null;
		ServletOutputStream outputStream = response.getOutputStream();
		try {
			List<List<String>> rows = new ArrayList<>();
			List<String> title = CollUtil.newArrayList("配件名称", "配件分类", "品牌", "规格型号","单位", "提交人", "提交时间", "更新时间");
			list.forEach(item -> {
				List<String> row = new ArrayList<>(8);
				row.add(Convert.toStr(item.getPartsName()));
				row.add(Convert.toStr(item.getMaintainPartsName()));
				row.add(Convert.toStr(item.getBrand()));
				row.add(Convert.toStr(item.getModel()));
				row.add(Convert.toStr(item.getUnit()));
				row.add(Convert.toStr(item.getRealName()));
				row.add(DateUtil.format(item.getCreateTime(), DatePattern.NORM_DATETIME_PATTERN));
				row.add(DateUtil.format(item.getUpdateTime(), DatePattern.NORM_DATETIME_PATTERN));
				rows.add(row);
			});
			excelWriter = ExcelUtil.getBigWriter(rows.size());
			excelWriter.setColumnWidth(0, 30);
			excelWriter.setColumnWidth(1, 30);
			excelWriter.setColumnWidth(2, 30);
			excelWriter.setColumnWidth(3, 30);
			excelWriter.setColumnWidth(4, 30);
			excelWriter.setColumnWidth(5, 30);
			excelWriter.setColumnWidth(6, 40);
			excelWriter.setColumnWidth(7, 40);

			// 单独设置头部
			excelWriter.writeHeadRow(title);
			String fileName = "配件信息信息_" + DateUtil.format(new Date(), "yyyyMMddHHmmss") + ".xlsx";
			CommonUtil.setResponseHeader(response, fileName);
			excelWriter.write(rows, true);
			excelWriter.flush(outputStream, true);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (excelWriter != null) {
				excelWriter.close();
			}
			outputStream.close();
		}
	}

	@Override
	@TenantIgnore
	public List<PartnerCloudPartsListDTO> selectPartsList() {
		return baseMapper.selectPartnerCloudPartsList();
	}

	/**
	 * 初始化excel模板
	 * @return
	 */
	private XSSFWorkbook initExcelTemplate() {
		XSSFWorkbook workbook = null;
		try {
			//读取源文件
			ClassPathResource resource = new ClassPathResource("templates/parts_info_template.xlsx");
			workbook = new XSSFWorkbook(resource.getInputStream());
		} catch (IOException e) {
			e.printStackTrace();
			throw new ServiceException("配件信息模板文件异常！");
		}

		// 创建sheet
		XSSFSheet sheet = workbook.getSheetAt(0);
		// 自定义样式
		XSSFCellStyle style = workbook.createCellStyle();
		style.setFillForegroundColor(HSSFColor.GREY_25_PERCENT.index);
		//前景填充模式
		style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
		// 创建一个居中格式
		style.setAlignment(HorizontalAlignment.CENTER);
		// 垂直居中
		style.setVerticalAlignment(VerticalAlignment.CENTER);
		// 新建一个字体
		XSSFFont whiteFont = workbook.createFont();
		whiteFont.setColor(IndexedColors.BLACK.getIndex());
		whiteFont.setFontHeightInPoints((short) 12);
		style.setFont(whiteFont);

		XSSFRow row = sheet.createRow(1);
		// 设置行高
		row.setHeightInPoints(20);
		for (int i = 0; i < HEAD_TITLE.length; i++) {
			XSSFCell cell = row.createCell(i);
			cell.setCellStyle(style);
			cell.setCellValue(HEAD_TITLE[i]);
		}

		// 设置单位下拉选择数据源
		String[] units = {"台","套","件","个","盒","箱","瓶","包"};
		XSSFDataValidationHelper dvHelper = new XSSFDataValidationHelper(sheet);
		XSSFDataValidationConstraint dvConstraint = (XSSFDataValidationConstraint) dvHelper.createExplicitListConstraint(units);
		// 四个参数依次的意思是：开始行，结束行，开始列，结束列
		CellRangeAddressList addressList =  new CellRangeAddressList(2, 1000, 6, 6);
		XSSFDataValidation validation  = (XSSFDataValidation) dvHelper.createValidation(dvConstraint, addressList);
		validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
		// 当单元格中的值不是下拉选中的值的一个提示
		validation.createErrorBox("error", "请选择正确数据");
		validation.setShowErrorBox(true);
		validation.setShowPromptBox(true);
		sheet.addValidationData(validation);

		//三级联动 sheet
		XSSFSheet mapSheet = workbook.createSheet("selectData");
		//三级关系sheet，设置sheet是否隐藏 true:是，false:否
		workbook.setSheetHidden(workbook.getSheetIndex(mapSheet), true);

		// 读取下拉数据(只读取三级数据)
		List<MaintainParts> partsList = maintainPartsService.list(Wrappers.<MaintainParts>lambdaQuery().le(MaintainParts :: getLevel, 3));
		Map<Long, MaintainParts> map = partsList.stream().collect(Collectors.toMap(t -> t.getId(), t -> t));
		//所有一级
		List<String> mapOneList = partsList.stream().filter(t -> t.getLevel() == 1).map(t -> t.getName()).collect(Collectors.toCollection(LinkedList :: new));
		//二、三级关系map
		Map<String, List<String>> selectMap = new LinkedHashMap<>(256);
		Map<Long,List<MaintainParts>> parentIdMap = partsList.stream().filter(t -> t.getLevel() > 1).collect(Collectors.groupingBy(t -> t.getParentId()));
		parentIdMap.forEach((k,v) -> {
			if (map.containsKey(k)) {
				selectMap.put(map.get(k).getName(), v.stream().map(t -> t.getName()).collect(Collectors.toList()));
			}
		});

		// 写入数据 将数据写入隐藏的sheet中并做好关联关系
		PoiSelectExcelUtil.writeData(workbook, mapSheet, mapOneList, selectMap);
		// 设置数据有效性
		PoiSelectExcelUtil.setDataValid(workbook, sheet, mapOneList);

		return workbook;
	}

}
